{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"http://hilpisch.com/tpq_logo.png\" alt=\"The Python Quants\" width=\"35%\" align=\"right\" border=\"0\"><br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python for Finance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Analyze Big Financial Data**\n",
    "\n",
    "O'Reilly (2014)\n",
    "\n",
    "Yves Hilpisch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img style=\"border:0px solid grey;\" src=\"http://hilpisch.com/python_for_finance.png\" alt=\"Python for Finance\" width=\"30%\" align=\"left\" border=\"0\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Buy the book ** |\n",
    "<a href='http://shop.oreilly.com/product/0636920032441.do' target='_blank'>O'Reilly</a> |\n",
    "<a href='http://www.amazon.com/Yves-Hilpisch/e/B00JCYHHJM' target='_blank'>Amazon</a>\n",
    "\n",
    "**All book codes & IPYNBs** |\n",
    "<a href=\"http://oreilly.quant-platform.com\">http://oreilly.quant-platform.com</a>\n",
    "\n",
    "**The Python Quants GmbH** | <a href='http://tpq.io' target='_blank'>http://tpq.io</a>\n",
    "\n",
    "**Contact us** | <a href='mailto:pff@tpq.io'>pff@tpq.io</a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "# Performance Python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pylab import plt\n",
    "plt.style.use('seaborn')\n",
    "import matplotlib as mpl\n",
    "mpl.rcParams['font.family'] = 'serif'\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "uuid": "0e214dc5-2294-4526-847b-02d2b0b8ecf3"
   },
   "outputs": [],
   "source": [
    "def perf_comp_data(func_list, data_list, rep=3, number=1):\n",
    "    ''' Function to compare the performance of different functions.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    func_list : list\n",
    "        list with function names as strings\n",
    "    data_list : list\n",
    "        list with data set names as strings\n",
    "    rep : int\n",
    "        number of repetitions of the whole comparison\n",
    "    number : int\n",
    "        number of executions for every function\n",
    "    '''\n",
    "    from timeit import repeat\n",
    "    res_list = {}\n",
    "    for name in enumerate(func_list):\n",
    "        stmt = name[1] + '(' + data_list[name[0]] + ')'\n",
    "        setup = \"from __main__ import \" + name[1] + ', ' \\\n",
    "                                    + data_list[name[0]]\n",
    "        results = repeat(stmt=stmt, setup=setup,\n",
    "                         repeat=rep, number=number)\n",
    "        res_list[name[1]] = sum(results) / rep\n",
    "    res_sort = sorted(res_list.items(),\n",
    "                      key=lambda x: (x[1], x[0]))\n",
    "    for item in res_sort:\n",
    "        rel = item[1] / res_sort[0][1]\n",
    "        print ('function: ' + item[0] +\n",
    "              ', av. time sec: %9.5f, ' % item[1]\n",
    "            + 'relative: %6.1f' % rel)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Python Paradigms and Performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "uuid": "b3334105-e18d-4510-a9c6-e879b8fb2484"
   },
   "outputs": [],
   "source": [
    "from math import *\n",
    "def f(x):\n",
    "    return abs(cos(x)) ** 0.5 + sin(2 + 3 * x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "uuid": "06bd686b-de54-43e3-bd1f-4ef5fe006036"
   },
   "outputs": [],
   "source": [
    "I = 500000\n",
    "a_py = range(I)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "uuid": "887f94a7-954e-4a22-ad08-529686619217"
   },
   "outputs": [],
   "source": [
    "def f1(a):\n",
    "    res = []\n",
    "    for x in a:\n",
    "        res.append(f(x))\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "uuid": "50d65fdf-ba74-4c09-910e-19560b3e2b78"
   },
   "outputs": [],
   "source": [
    "def f2(a):\n",
    "    return [f(x) for x in a]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "uuid": "c2dd4ea6-d303-4392-af37-a83411085463"
   },
   "outputs": [],
   "source": [
    "def f3(a):\n",
    "    ex = 'abs(cos(x)) ** 0.5 + sin(2 + 3 * x)'\n",
    "    return [eval(ex) for x in a]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "uuid": "f3bd9b18-bab2-433f-ab3c-4d7ffed24107"
   },
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "uuid": "e53390a4-d6ff-4083-bab3-c47955becc67"
   },
   "outputs": [],
   "source": [
    "a_np = np.arange(I)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "uuid": "701d5d58-e35c-4440-aa57-7bf96bcd546f"
   },
   "outputs": [],
   "source": [
    "def f4(a):\n",
    "    return (np.abs(np.cos(a)) ** 0.5 +\n",
    "            np.sin(2 + 3 * a))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "uuid": "0df68c60-ab06-4024-b7f8-cf3dacb67668"
   },
   "outputs": [],
   "source": [
    "import numexpr as ne"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "uuid": "ff4cbc78-b7c7-4a45-ab60-d143e59aa9f5"
   },
   "outputs": [],
   "source": [
    "def f5(a):\n",
    "    ex = 'abs(cos(a)) ** 0.5 + sin(2 + 3 * a)'\n",
    "    ne.set_num_threads(1)\n",
    "    return ne.evaluate(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "uuid": "c4732030-2cf0-4fc2-923c-d341d7e70c09"
   },
   "outputs": [],
   "source": [
    "def f6(a):\n",
    "    ex = 'abs(cos(a)) ** 0.5 + sin(2 + 3 * a)'\n",
    "    ne.set_num_threads(16)\n",
    "    return ne.evaluate(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "uuid": "cf287924-ddbc-4d2d-a1cc-5302da462211"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 9.85 s, sys: 170 ms, total: 10 s\n",
      "Wall time: 10.4 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "r1 = f1(a_py)\n",
    "r2 = f2(a_py)\n",
    "r3 = f3(a_py)\n",
    "r4 = f4(a_np)\n",
    "r5 = f5(a_np)\n",
    "r6 = f6(a_np)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "uuid": "77a761b5-8ba4-4377-8b97-36d4453cc3a5"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "uuid": "00b7a34b-224c-494b-8479-17e4f778e56b"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "uuid": "6b48a086-0d1c-46ca-9560-57c4f22373f2"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "uuid": "3513b09a-7f41-413a-a3d2-5dc58487f5fa"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "uuid": "fe9798c0-3fc6-4a3e-94a9-65cbbe588709"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "uuid": "2239301d-cf3c-4be3-b310-9001e5d346f2"
   },
   "outputs": [],
   "source": [
    "func_list = ['f1', 'f2', 'f3', 'f4', 'f5', 'f6']\n",
    "data_list = ['a_py', 'a_py', 'a_py', 'a_np', 'a_np', 'a_np']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "uuid": "fd4bad14-de4c-4767-8f80-4ce8754a8531"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: f6, av. time sec:   0.00610, relative:    1.0\n",
      "function: f5, av. time sec:   0.01247, relative:    2.0\n",
      "function: f4, av. time sec:   0.02382, relative:    3.9\n",
      "function: f2, av. time sec:   0.37416, relative:   61.4\n",
      "function: f1, av. time sec:   0.45315, relative:   74.3\n",
      "function: f3, av. time sec:   9.59223, relative: 1573.6\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Memory Layout and Performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "uuid": "eae4309a-435c-4b76-a7cb-3d493ceb16a5"
   },
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "uuid": "db307e1f-15f5-44ab-a35b-9f587e86f89f"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.,  0.,  0.],\n",
       "       [ 0.,  0.,  0.],\n",
       "       [ 0.,  0.,  0.]])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.zeros((3, 3), dtype=np.float64, order='C')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    },
    "uuid": "ef951c18-ade7-4525-ae8f-6d1286aaa261"
   },
   "outputs": [],
   "source": [
    "c = np.array([[ 1.,  1.,  1.],\n",
    "              [ 2.,  2.,  2.],\n",
    "              [ 3.,  3.,  3.]], order='C')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "uuid": "dd75848c-cbaf-4ff3-a023-b444fbaa2324"
   },
   "outputs": [],
   "source": [
    "f = np.array([[ 1.,  1.,  1.],\n",
    "              [ 2.,  2.,  2.],\n",
    "              [ 3.,  3.,  3.]], order='F')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "uuid": "10424fde-31e8-4781-a7d8-eb8ec4469c10"
   },
   "outputs": [],
   "source": [
    "x = np.random.standard_normal((3, 150000))\n",
    "C = np.array(x, order='C')\n",
    "F = np.array(x, order='F')\n",
    "x = 0.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "uuid": "82dda14d-3d8e-4953-a33b-ffece16a9de5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "506 µs ± 58.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit C.sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "uuid": "8e042104-1930-4a07-9467-6569f583414e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "268 µs ± 50.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit C.sum(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    },
    "uuid": "311be903-c939-4c1d-a8f7-22803299bd1d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.16 ms ± 482 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit C.std(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "uuid": "c032c8a5-c0af-4b75-a4a4-28d8b867738e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.44 ms ± 137 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit C.std(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "uuid": "65225c7d-e0ce-41f8-801d-16157e7725d4"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.72 ms ± 159 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit F.sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "uuid": "11ce9f40-204a-4271-83b5-2f68c1a66415"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.98 ms ± 250 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit F.sum(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "uuid": "9d9b7754-f8db-4477-a31b-38776c94ba03"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8.97 ms ± 1.01 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit F.std(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "uuid": "1ce86c04-2304-424d-b82c-409972b3f876"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8.16 ms ± 329 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
     ]
    }
   ],
   "source": [
    "%timeit F.std(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    },
    "uuid": "5e25ec32-9655-4413-bc54-75adfa36daae"
   },
   "outputs": [],
   "source": [
    "C = 0.0; F = 0.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Parallel Computing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiprocessing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "uuid": "d2fa8566-0084-408e-a2c8-dc8ec58ef460"
   },
   "outputs": [],
   "source": [
    "import multiprocessing as mp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "uuid": "21c07619-d632-4208-9efd-a80ed68036a7"
   },
   "outputs": [],
   "source": [
    "import math\n",
    "def simulate_geometric_brownian_motion(p):\n",
    "    M, I = p\n",
    "      # time steps, paths\n",
    "    S0 = 100; r = 0.05; sigma = 0.2; T = 1.0\n",
    "      # model parameters\n",
    "    dt = T / M\n",
    "    paths = np.zeros((M + 1, I))\n",
    "    paths[0] = S0\n",
    "    for t in range(1, M + 1):\n",
    "        paths[t] = paths[t - 1] * np.exp((r - 0.5 * sigma ** 2) * dt +\n",
    "                    sigma * math.sqrt(dt) * np.random.standard_normal(I))\n",
    "    return paths"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "uuid": "3b90f803-483d-4042-8365-831b14ae1284"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 100.        ,  100.        ],\n",
       "       [ 107.75994389,   96.38974629],\n",
       "       [ 111.36869054,  104.04967034],\n",
       "       [  97.36585627,   99.81282855],\n",
       "       [ 100.33598469,   85.5209064 ],\n",
       "       [  88.58477948,   76.08336793]])"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths = simulate_geometric_brownian_motion((5, 2))\n",
    "paths"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "uuid": "0a4b66c3-3c89-49ea-871e-8c2856cc4d4e"
   },
   "outputs": [],
   "source": [
    "I = 10000  # number of paths\n",
    "M = 50  # number of time steps\n",
    "t = 20  # number of tasks/simulations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "uuid": "428591ca-0dbd-4d12-bcfc-d611883df0cd"
   },
   "outputs": [],
   "source": [
    "# running on server with 4 cores\n",
    "from time import time\n",
    "times = []\n",
    "for w in range(1, 5):\n",
    "    t0 = time()\n",
    "    pool = mp.Pool(processes=w)\n",
    "      # the pool of workers\n",
    "    result = pool.map(simulate_geometric_brownian_motion, t * [(M, I), ])\n",
    "      # the mapping of the function to the list of parameter tuples\n",
    "    times.append(time() - t0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "uuid": "8ef9b38e-b367-447f-a5ac-cb05fb45dc67"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5,1,'20 Monte Carlo simulations')"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x113388cc0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(range(1, 5), times)\n",
    "plt.plot(range(1, 5), times, 'ro')\n",
    "plt.grid(True)\n",
    "plt.xlabel('number of processes')\n",
    "plt.ylabel('time in seconds')\n",
    "plt.title('%d Monte Carlo simulations' % t)\n",
    "# tag: multi_proc\n",
    "# title: Comparison execution speed dependent on the number of threads used (4 core machine)\n",
    "# size: 60"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Dynamic Compiling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Introductory Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "uuid": "3f198c70-faa5-44a9-b886-227b97c2363d"
   },
   "outputs": [],
   "source": [
    "from math import cos, log\n",
    "def f_py(I, J):\n",
    "    res = 0\n",
    "    for i in range(I):\n",
    "        for j in range (J):\n",
    "            res += int(cos(log(1)))\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "uuid": "7ee49c0c-d153-445f-be3c-c1318e4766c8"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 2.39 s, sys: 17.2 ms, total: 2.41 s\n",
      "Wall time: 2.41 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "6250000"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "I, J = 2500, 2500\n",
    "%time f_py(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "uuid": "1be826e5-f072-4476-b829-b12fb1b70086"
   },
   "outputs": [],
   "source": [
    "def f_np(I, J):\n",
    "    a = np.ones((I, J), dtype=np.float64)\n",
    "    return int(np.sum(np.cos(np.log(a)))), a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "uuid": "211eb198-f43d-4a89-b362-6e9507c099de"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 117 ms, sys: 67.3 ms, total: 185 ms\n",
      "Wall time: 183 ms\n"
     ]
    }
   ],
   "source": [
    "%time res, a = f_np(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "uuid": "cfd19f8d-bf9a-442b-b3ef-2dfdce9ac3a9"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50000000"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a.nbytes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "uuid": "72e7a2be-97d0-4391-bc6e-3be881bf17a8"
   },
   "outputs": [],
   "source": [
    "import numba as nb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "uuid": "125becea-83ae-4718-9064-e1c3424888c3"
   },
   "outputs": [],
   "source": [
    "f_nb = nb.jit(f_py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "uuid": "aeb3d63e-7682-4d89-8c3a-0d5d8e649467"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 113 ms, sys: 8.23 ms, total: 121 ms\n",
      "Wall time: 120 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "6250000"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time f_nb(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "uuid": "25bc3375-a682-4f25-a303-b756848565d9"
   },
   "outputs": [],
   "source": [
    "func_list = ['f_py', 'f_np', 'f_nb']\n",
    "data_list = 3 * ['I, J']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "uuid": "71e376e3-cd8c-47a8-93dc-5daeabd59e92"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: f_nb, av. time sec:   0.00000, relative:    1.0\n",
      "function: f_np, av. time sec:   0.13171, relative: 82319.1\n",
      "function: f_py, av. time sec:   2.20040, relative: 1375253.4\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Binomial Option Pricing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "uuid": "7340fbcb-1355-4862-ba6d-8bc04c04b014"
   },
   "outputs": [],
   "source": [
    "# model & option Parameters\n",
    "S0 = 100.  # initial index level\n",
    "T = 1.  # call option maturity\n",
    "r = 0.05  # constant short rate\n",
    "vola = 0.20  # constant volatility factor of diffusion\n",
    "\n",
    "# time parameters\n",
    "M = 1000  # time steps\n",
    "dt = T / M  # length of time interval\n",
    "df = exp(-r * dt)  # discount factor per time interval\n",
    "\n",
    "# binomial parameters\n",
    "u = exp(vola * sqrt(dt))  # up-movement\n",
    "d = 1 / u  # down-movement\n",
    "q = (exp(r * dt) - d) / (u - d)  # martingale probability"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "uuid": "435a8933-8bbf-4e17-9f85-ff75f5997916"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "def binomial_py(strike):\n",
    "    ''' Binomial option pricing via looping.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    strike : float\n",
    "        strike price of the European call option\n",
    "    '''\n",
    "    # LOOP 1 - Index Levels\n",
    "    S = np.zeros((M + 1, M + 1), dtype=np.float64)\n",
    "      # index level array\n",
    "    S[0, 0] = S0\n",
    "    z1 = 0\n",
    "    for j in range(1, M + 1, 1):\n",
    "        z1 = z1 + 1\n",
    "        for i in range(z1 + 1):\n",
    "            S[i, j] = S[0, 0] * (u ** j) * (d ** (i * 2))\n",
    "            \n",
    "    # LOOP 2 - Inner Values\n",
    "    iv = np.zeros((M + 1, M + 1), dtype=np.float64)\n",
    "      # inner value array\n",
    "    z2 = 0\n",
    "    for j in range(0, M + 1, 1):\n",
    "        for i in range(z2 + 1):\n",
    "            iv[i, j] = max(S[i, j] - strike, 0)\n",
    "        z2 = z2 + 1\n",
    "        \n",
    "    # LOOP 3 - Valuation\n",
    "    pv = np.zeros((M + 1, M + 1), dtype=np.float64)\n",
    "      # present value array\n",
    "    pv[:, M] = iv[:, M]  # initialize last time point\n",
    "    z3 = M + 1\n",
    "    for j in range(M - 1, -1, -1):\n",
    "        z3 = z3 - 1\n",
    "        for i in range(z3):\n",
    "            pv[i, j] = (q * pv[i, j + 1] +\n",
    "                        (1 - q) * pv[i + 1, j + 1]) * df\n",
    "    return pv[0, 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "uuid": "9a193f03-655e-40ea-bac4-57869ddf4745"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.58 s, sys: 30 ms, total: 1.61 s\n",
      "Wall time: 1.64 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.449"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time round(binomial_py(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "uuid": "8b7b292f-5d43-4462-9ac1-5ceb65ee078f"
   },
   "outputs": [],
   "source": [
    "def binomial_np(strike):\n",
    "    ''' Binomial option pricing with NumPy.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    strike : float\n",
    "        strike price of the European call option\n",
    "    '''\n",
    "    # Index Levels with NumPy\n",
    "    mu = np.arange(M + 1)\n",
    "    mu = np.resize(mu, (M + 1, M + 1))\n",
    "    md = np.transpose(mu)\n",
    "    mu = u ** (mu - md)\n",
    "    md = d ** md\n",
    "    S = S0 * mu * md\n",
    "    \n",
    "    # Valuation Loop\n",
    "    pv = np.maximum(S - strike, 0)\n",
    "\n",
    "    z = 0\n",
    "    for t in range(M - 1, -1, -1):  # backwards iteration\n",
    "        pv[0:M - z, t] = (q * pv[0:M - z, t + 1]\n",
    "                        + (1 - q) * pv[1:M - z + 1, t + 1]) * df\n",
    "        z += 1\n",
    "    return pv[0, 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "uuid": "61e7450e-58fb-4b5f-9f68-c95e82fe8070"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "M = 4  # four time steps only\n",
    "mu = np.arange(M + 1)\n",
    "mu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "uuid": "0cad7263-b392-4b70-8e41-1b212c5bd076"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4]])"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mu = np.resize(mu, (M + 1, M + 1))\n",
    "mu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {
    "uuid": "720863b6-f28c-46ad-8bf2-1743a0b09efc"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 0, 0, 0, 0],\n",
       "       [1, 1, 1, 1, 1],\n",
       "       [2, 2, 2, 2, 2],\n",
       "       [3, 3, 3, 3, 3],\n",
       "       [4, 4, 4, 4, 4]])"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "md = np.transpose(mu)\n",
    "md"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "uuid": "cad9aa29-b19d-4ab6-a26a-4664fa166a60"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.   ,  1.006,  1.013,  1.019,  1.026],\n",
       "       [ 0.994,  1.   ,  1.006,  1.013,  1.019],\n",
       "       [ 0.987,  0.994,  1.   ,  1.006,  1.013],\n",
       "       [ 0.981,  0.987,  0.994,  1.   ,  1.006],\n",
       "       [ 0.975,  0.981,  0.987,  0.994,  1.   ]])"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mu = u ** (mu - md)\n",
    "mu.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {
    "uuid": "fcf799c2-3311-4ca6-8293-1485caabe8a0"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.   ,  1.   ,  1.   ,  1.   ,  1.   ],\n",
       "       [ 0.994,  0.994,  0.994,  0.994,  0.994],\n",
       "       [ 0.987,  0.987,  0.987,  0.987,  0.987],\n",
       "       [ 0.981,  0.981,  0.981,  0.981,  0.981],\n",
       "       [ 0.975,  0.975,  0.975,  0.975,  0.975]])"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "md = d ** md\n",
    "md.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {
    "uuid": "e6998500-9c10-4b10-85a2-1766153bd20a"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 100.   ,  100.634,  101.273,  101.915,  102.562],\n",
       "       [  98.743,   99.37 ,  100.   ,  100.634,  101.273],\n",
       "       [  97.502,   98.121,   98.743,   99.37 ,  100.   ],\n",
       "       [  96.276,   96.887,   97.502,   98.121,   98.743],\n",
       "       [  95.066,   95.669,   96.276,   96.887,   97.502]])"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "S = S0 * mu * md\n",
    "S.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {
    "uuid": "29f7228e-7751-46dd-886e-e74c4c28d56b"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 91.5 ms, sys: 15.2 ms, total: 107 ms\n",
      "Wall time: 106 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.449"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "M = 1000  # reset number of time steps\n",
    "%time round(binomial_np(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {
    "uuid": "0551702a-ecbb-46ec-8a65-eca2b9788adc"
   },
   "outputs": [],
   "source": [
    "binomial_nb = nb.jit(binomial_py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "uuid": "ea4be56f-18d2-46c0-8a56-23c24b7c1c9a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 746 ms, sys: 32.3 ms, total: 778 ms\n",
      "Wall time: 784 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.449"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time round(binomial_nb(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {
    "uuid": "6b0286e5-8f41-47e3-897a-0e9cf06ce35f"
   },
   "outputs": [],
   "source": [
    "func_list = ['binomial_py', 'binomial_np', 'binomial_nb']\n",
    "K = 100.\n",
    "data_list = 3 * ['K']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {
    "uuid": "bf398a3d-c2ce-4cdd-ae1e-0c7e49afcdf7"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: binomial_np, av. time sec:   0.10355, relative:    1.0\n",
      "function: binomial_nb, av. time sec:   0.16534, relative:    1.6\n",
      "function: binomial_py, av. time sec:   1.46389, relative:   14.1\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Static Compiling with Cython"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {
    "uuid": "42984b49-a970-4dd1-9ea0-cad5cae02323"
   },
   "outputs": [],
   "source": [
    "def f_py(I, J):\n",
    "    res = 0.  # we work on a float object\n",
    "    for i in range(I):\n",
    "        for j in range (J * I):\n",
    "            res += 1\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "uuid": "61275ff2-94d2-4e94-bdb2-caf356435074"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 7.73 s, sys: 66.2 ms, total: 7.8 s\n",
      "Wall time: 7.84 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "I, J = 500, 500\n",
    "%time f_py(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {
    "uuid": "215b732a-4fe7-4901-b054-e6c3b6ca2161"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, <pyximport.pyximport.PyxImporter at 0x1170e9470>)"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pyximport\n",
    "pyximport.install()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {
    "uuid": "5adec88a-d48c-4972-abb7-0a6492384f13"
   },
   "outputs": [],
   "source": [
    "import sys\n",
    "sys.path.append('data/')\n",
    "  # path to the Cython script\n",
    "  # not needed if in same directory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {
    "uuid": "a28ef97f-7867-4163-bba1-87706aa348ad"
   },
   "outputs": [],
   "source": [
    "from nested_loop import f_cy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {
    "uuid": "cb96d228-6db0-4d9d-ac7e-852698da0098"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 203 ms, sys: 2.87 ms, total: 206 ms\n",
      "Wall time: 205 ms\n"
     ]
    }
   ],
   "source": [
    "%time res = f_cy(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {
    "uuid": "03e8d77b-b0d7-4dab-8b9d-f544dcd10d91"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {
    "uuid": "ce1d0d99-0fc1-4062-9f77-de253fa41ef7"
   },
   "outputs": [],
   "source": [
    "%load_ext Cython"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {
    "uuid": "2a20c9e7-69e5-47e5-a5f9-2271e42661e3"
   },
   "outputs": [],
   "source": [
    "%%cython\n",
    "#\n",
    "# Nested loop example with Cython\n",
    "#\n",
    "def f_cy(int I, int J):\n",
    "    cdef double res = 0\n",
    "    # double float much slower than int or long\n",
    "    for i in range(I):\n",
    "        for j in range (J * I):\n",
    "            res += 1\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {
    "uuid": "44344a84-ccd1-47ce-a23a-69d70cdb6704"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 188 ms, sys: 2.13 ms, total: 190 ms\n",
      "Wall time: 189 ms\n"
     ]
    }
   ],
   "source": [
    "%time res = f_cy(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {
    "uuid": "a1b8b484-49cd-4872-ae0d-d547d6d965d1"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {
    "uuid": "f0984dae-8739-44db-bf67-dd9fc4ff058d"
   },
   "outputs": [],
   "source": [
    "import numba as nb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {
    "uuid": "7f8097cb-dcca-4280-9371-a49a34c91f28"
   },
   "outputs": [],
   "source": [
    "f_nb = nb.jit(f_py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {
    "uuid": "ab30b87b-1ef5-41b7-918d-95e096cbafaf"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 266 ms, sys: 6.92 ms, total: 272 ms\n",
      "Wall time: 277 ms\n"
     ]
    }
   ],
   "source": [
    "%time res = f_nb(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {
    "uuid": "d8c59388-1209-4d26-af73-13ee504a89f2"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 82,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {
    "uuid": "86494215-2f3c-4299-83dd-a74b58847658"
   },
   "outputs": [],
   "source": [
    "func_list = ['f_py', 'f_cy', 'f_nb']\n",
    "I, J = 500, 500\n",
    "data_list = 3 * ['I, J']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {
    "uuid": "7fdcda66-f27e-4f77-a103-b9c862ed1b33"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: f_nb, av. time sec:   0.22109, relative:    1.0\n",
      "function: f_cy, av. time sec:   0.23009, relative:    1.0\n",
      "function: f_py, av. time sec:   8.78361, relative:   39.7\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Generation of Random Numbers on GPUs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Conclusions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Further Reading"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"http://hilpisch.com/tpq_logo.png\" alt=\"The Python Quants\" width=\"35%\" align=\"right\" border=\"0\"><br>\n",
    "\n",
    "<a href=\"http://tpq.io\" target=\"_blank\">http://tpq.io</a> | <a href=\"http://twitter.com/dyjh\" target=\"_blank\">@dyjh</a> | <a href=\"mailto:training@tpq.io\">training@tpq.io</a>\n",
    "\n",
    "**Quant Platform** |\n",
    "<a href=\"http://quant-platform.com\">http://quant-platform.com</a>\n",
    "\n",
    "**Python for Finance** |\n",
    "<a href=\"http://python-for-finance.com\" target=\"_blank\">Python for Finance @ O'Reilly</a>\n",
    "\n",
    "**Derivatives Analytics with Python** |\n",
    "<a href=\"http://derivatives-analytics-with-python.com\" target=\"_blank\">Derivatives Analytics @ Wiley Finance</a>\n",
    "\n",
    "**Listed Volatility and Variance Derivatives** |\n",
    "<a href=\"http://lvvd.tpq.io\" target=\"_blank\">Listed VV Derivatives @ Wiley Finance</a>\n",
    "\n",
    "**Python Training** |\n",
    "<a href=\"http://training.tpq.io\" target=\"_blank\">Python for Finance University Certificate</a>"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
